#include<string>
#include<boost/asio.hpp>
using boost::asio::ip::tcp;
#include <iostream>
#include <functional>
#include "trim.h"
#include "join.h"
#include "sink.h"
#include "transform.h"
#include "service.h"
#include "session.h"
#include "values.h"
#include "join.h"
#include "filter.h"
#include "expected.h"
#include "mtry.h"
#include <nlohmann/json.hpp>
using json = nlohmann::json;
using expected_json = expected<json, std::exception_ptr>;
// 构造
template <typename Sender, typename Function>
auto sink(Sender&& sender, Function&& function)
{
    return detail::sink_impl<Sender, Function>(
        std::forward<Sender>(sender),
        std::forward<Function>(function)
    );
}
// 构造
template <typename Function>
auto sink(Function&& function)
{
    return detail::sink_helper<Function>{
        std::forward<Function>(function)};
}

// 用管道指定第一个参数
template <typename Sender, typename Function>
auto operator|(Sender&& sender,detail::sink_helper<Function> sink)
{
    return detail::sink_impl<Sender, Function>(
        std::forward<Sender>(sender),sink.function
    );
}

template <typename Sender, typename Transformation>
auto transform(Sender&& sender, Transformation&& transformation)
{
    return detail::transform_impl<Sender, Transformation>(
        std::forward<Sender>(sender),
        std::forward<Transformation>(transformation));
}
template <typename Transformation>
auto transform(Transformation&& transformation)
{
    return detail::transform_helper<Transformation>{
        std::forward<Transformation>(transformation)};
}

template <typename Sender, typename Transformation>
auto operator|(Sender&& sender,
                detail::transform_helper<Transformation> transformation)
{
    return detail::transform_impl<Sender, Transformation>(
        std::forward<Sender>(sender), transformation.function);
}

inline auto join()
    {
        return detail::join_helper();
    }

template <typename Sender>
auto operator|(Sender&& sender,
                detail::join_helper)
{
    return detail::join_impl<Sender>(std::forward<Sender>(sender));
}

template <typename Predicate>
    auto filter(Predicate&& predicate)
    {
        return detail::filter_helper<Predicate>{
            std::forward<Predicate>(predicate)};
    }

template <typename Sender, typename Predicate>
auto operator| (Sender&& sender, detail::filter_helper<Predicate> filter)
{
    return detail::filter_impl<Sender, Predicate>(
        std::forward<Sender>(sender), filter.predicate);
}


struct bookmark_t {
    std::string url;
    std::string text;
};

/**
 * Type that contains a bookmark or an error (an exception)
 */
using expected_bookmark = expected<bookmark_t, std::exception_ptr>;

/**
 * Tries to read the bookmark info from JSON. In the case
 * of failure, it will return an exception.
 */
expected_bookmark bookmark_from_json(const json& data)
{
    return mtry([&] {
            return bookmark_t { data.at("FirstURL"), data.at("Text") };
        });
}

int main(int argc, char* argv[])
{
    boost::asio::io_service event_loop;
    // 创建服务并与宿Actor相连接
    auto pipeline = sink(service(event_loop),
                [] (const auto& message) {
                    std::cerr << message <<std::endl;
                });
    auto sink_to_cerr = sink([] (const auto& message) {
        std::cerr << message << std::endl;
    });
    auto pipeline2 = service(event_loop) | sink_to_cerr;
    auto pipeline3 = service(event_loop) | transform(trim) | sink_to_cerr;
    auto pipeline4 = values{42} | sink_to_cerr;
    auto pipeline5 = values{42042, 42043, 42044} | transform([&](int port) {
        return service(event_loop, port);
    }) | join() | sink_to_cerr;
    auto pipeline6 = service(event_loop) | transform(trim) | filter([] (const std::string& message) {
        return (message.length() > 0 && message[0] != '#');
    }) | sink_to_cerr;
    // 转换字符串，结果要么是一个json，要么是一个指向异常的指针
    auto pipeline7 = service(event_loop) | transform(trim) | filter([] (const std::string& message) {
        return(message.length() > 0 && message[0] != '#');
    }) | transform([] (const std::string& message) {
        return mtry([&] {
            return json::parse(message);
        });
    }) | sink_to_cerr;


    auto pipeline8 = service(event_loop) | transform(trim) | filter([] (const std::string& message) {
        return(message.length() > 0 && message[0] != '#');
    }) | transform([] (const std::string& message) {
        return mtry([&] {
            return json::parse(message);
        });
    }) 

    | filter(&expected_json::is_valid)
    | transform(&expected_json.get)
    | transform(bookmark_from_json)
    | filter(&expected_bookmark::is_valid)
    | transform(&expected_bookmark::get)
    | sink_to_cerr;

    auto pipeline9 = service(event_loop) | transform(trim) 
    | filter([] (const std::string& message) {
        return(message.length() > 0 && message[0] != '#');
    })
    | transform([] (const std::string& message) {
        return mtry([&] {
            return json::parse(message);
        });
    }) 
    | transform([] (const auto& exp_json) {
        return mbind(exp_json, bookmark_from_json);
    });

    auto transformc = [](auto f) {
            return transform(lift_with_client(f));
        };
    auto filterc = [](auto f) {
            return filter(apply_with_client(f));
        };
     auto pipeline10 =
        service(event_loop)
            | transformc(trim)

            // Ignoring comments and empty messages
            | filterc([] (const std::string &message) {
                return message.length() > 0 && message[0] != '#';
            })

            // Trying to parse the input
            | transformc([] (const std::string &message) {
                return mtry([&] {
                    return json::parse(message);
                });
            })

            // Converting the result into the bookmark
            | transformc([] (const auto& exp) {
                return mbind(exp, bookmark_from_json);
            })

            | sink([] (const auto &message) {
                const auto exp_bookmark = message.value;

                if (!exp_bookmark) {
                    message.reply("ERROR: Request not understood\n");
                    return;
                }

                if (exp_bookmark->text.find("C++") != std::string::npos) {
                    message.reply("OK: " + to_string(exp_bookmark.get()) + "\n");
                } else {
                    message.reply("ERROR: Not a C++-related link\n");
                }
            });
    event_loop.run();

    
}
